﻿using Anno.EngineData;
using Anno.Rpc.Storage;
using Flurl.Http;
using NSwag;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Anno.Api.GateWay
{
    public class ApiSwagger
    {
        /// <summary>
        /// Anno文档转换成 swagger Json
        /// </summary>
        /// <param name="annoDocUrl"></param>
        /// <returns></returns>
        public static string SwaggerAnnoJson(string annoDocUrl = "http://49.232.112.70", string appName = "HelloWorld")
        {
            string swaggerJson = string.Empty;
            if (!string.IsNullOrWhiteSpace(annoDocUrl))
            {
                var apis = $"{annoDocUrl}/{Const.Enum.Eng.BasePath}/api/route/GetRoutingInfo?appName={appName}"
                    .WithHeader("Content-Type", "application/json")
                 .GetAsync().ReceiveJson<ActionResult<List<AnnoApiDoc>>>().Result;
                swaggerJson = BuildSwaggerApi(apis.OutputData, annoDocUrl, appName).ToJson();
            }
            return swaggerJson;
        }

        private static OpenApiDocument BuildSwaggerApi(List<AnnoApiDoc> apiDocs, string annoDocUrl, string appName)
        {
            var document = new OpenApiDocument()
            {
                Info = new OpenApiInfo
                {
                    Version = "1.0.0",
                    Title = $"Swagger ({appName})",
                },
                Host = annoDocUrl,
                BasePath = "/" + Const.Enum.Eng.BasePath,
                Tags = BuildOpenApiTags(apiDocs)
            };
            BuildOpenApiPaths(document, apiDocs);
            return document;
        }
        private static void BuildOpenApiPaths(OpenApiDocument document, List<AnnoApiDoc> apiDocs)
        {
            if (apiDocs != null)
            {
                foreach (var doc in apiDocs)
                {
                    var channel = Anno.Const.Penetrates.GetControllerName(doc.Channel);//doc.Channel.EndsWith("Service", StringComparison.OrdinalIgnoreCase) ? doc.Channel.Substring(0, doc.Channel.Length - 7) : doc.Channel;//
                    var router = Anno.Const.Penetrates.GetControllerName(doc.Router);//doc.Router.EndsWith("Module", StringComparison.OrdinalIgnoreCase) ? doc.Router.Substring(0, doc.Router.Length - 6) : doc.Router;//
                    var path = $"/{channel}/{router}/{doc.Method}";
                    var routePrefix = $"/{doc.RoutePrefix?.Trim('/')}";
                    if (routePrefix.Split('/').Length > path.Split('/').Length)
                        path = routePrefix;
                    if (!document.Paths.ContainsKey(path))
                        document.Paths.Add(path, BuildOpenApiPathItem(doc));
                }
            }
        }

        private static List<OpenApiTag> BuildOpenApiTags(List<AnnoApiDoc> apiDocs)
        {
            var tags = new List<OpenApiTag>();
            if (apiDocs != null)
            {
                //var channels = apiDocs.Select(tag => tag.Channel).Distinct();
                //foreach (var channel in channels)
                //{
                //    tags.Add(new OpenApiTag()
                //    {
                //        Name = channel,
                //        Description = channel
                //    });
                //}
                var channels = apiDocs.Where(w => !string.IsNullOrWhiteSpace(w.Method)).Select(tag => new { Channel = tag.Channel + "/" + tag.Router, Title = tag.Channel }).Distinct();
                foreach (var channel in channels)
                {
                    tags.Add(new OpenApiTag()
                    {
                        Name = channel.Channel,
                        Description = channel.Title
                    });
                }
            }
            return tags;
        }
        private static OpenApiPathItem BuildOpenApiPathItem(AnnoApiDoc doc)
        {
            var httpMethod = "POST";
            if (doc?.Value != null && !string.IsNullOrWhiteSpace(doc.Value.HttpMethod))
                httpMethod = doc.Value.HttpMethod;

            var pathItem = new OpenApiPathItem()
            {
                Parameters = new List<OpenApiParameter>(),
                Description = doc.Desc
            };
            if (doc?.Value?.Parameters != null)
            {
                foreach (var item in doc.Value.Parameters)
                {
                    var openApiParameter = new OpenApiParameter
                    {
                        Name = item.Name,
                        Format = item.ParameterType,
                        Kind = httpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase) ?
                        (doc.RoutePrefix.Contains("{" + item.Name + "}", StringComparison.OrdinalIgnoreCase) ? OpenApiParameterKind.Path : OpenApiParameterKind.Query)
                        : (IsValueType(item.ParameterType) ? OpenApiParameterKind.FormData : OpenApiParameterKind.Body),
                        Description = item.Desc,

                    };
                    pathItem.Parameters.Add(openApiParameter);
                }
            }
            pathItem.Summary = doc.Desc;
            var operation = new OpenApiOperation()
            {
                Summary = doc.Desc,
                Tags = new List<string>() { doc.Channel + "/" + doc.Router }
            };

            operation.Responses.Add("200", new OpenApiResponse()
            {
                Description = string.IsNullOrWhiteSpace(doc.Desc) ? "OK" : doc.Desc
            });
            pathItem.Add(httpMethod, operation);
            return pathItem;
        }
        private static bool IsValueType(string type)
        {
            var types = type.Split('.');
            type = types[types.Length - 1];
            switch (type.Trim().ToLower())
            {
                case "bool":
                case "byte":
                case "sbyte":
                case "char":
                case "decimal":
                case "double":
                case "single":
                case "float":
                case "int32":
                case "int":
                case "uint32":
                case "uint":
                case "int64":
                case "long":
                case "uint64":
                case "ulong":
                //case "object":
                case "int16":
                case "short":
                case "uint16":
                case "ushort":
                case "string":
                case "date":
                case "datetime":
                case "guid":
                    return true;
                default:
                    return false;
            }
        }
    }
}
